#pragma once

#include "EndGameDbCommon.h"
#include "Bool.h"
#include "BinomCoef.h"

#define MAXIDX4 100
#define MAXIDX5 500
#define MAXIDX6 1000
#define MAXIDX7 31000
#define MAXIDX8 44000

#define BLOCKNUM4 5000
#define BLOCKNUM5 120000
#define BLOCKNUM6 75000
#define BLOCKNUM7 500000
#define BLOCKNUM8 4600000

#define BLOCKNUM4_DTC 4000
#define BLOCKNUM5_DTC 60000
#define BLOCKNUM6_DTC 800000
#define BLOCKNUM7_DTC 8000000
#define BLOCKNUM8_DTC 4600000

#define AUTOLOADSIZE 0

namespace EndGameDB
{

// definition of a structure for compressed databases
typedef struct compresseddatabase
	{
	int ispresent;			// does db exist?
	int numberofblocks;		// how many disk blocks does this db need?
	int blockoffset;		// offset to calculate unique block id for the blocks of this db
	int firstblock;			// where is the first block in it's db?
	int startbyte;			// at this byte
	int databasesize;		// index range for this database
	int value;				// WIN/LOSS/DRAW if single value, UNKNOWN == 0 else
	int *idx;				// pointer to an array of index numbers, array is 
							// allocated dynamically, to size n
	int fp;					// which file is it in?
	} cprsubdb;

// definition of a structure for compressed databases
typedef struct dtcdatabase
	{
	int ispresent;			// does db exist?
	int numberofblocks;		// how many disk blocks does this db need?
	int blockoffset;		// offset to calculate unique block id for the blocks of this db
	int firstblock;			// where is the first block in it's db?
	int startbyte;			// at this byte
	int databasesize;		// index range for this database
	int *idx;				// pointer to an array of index numbers, array is 
							// allocated dynamically, to size n
	int fp;					// which file is it in?
	} dtcdb;


extern cprsubdb cprsubdatabase[MAXPIECE+1][MAXPIECE+1][MAXPIECE+1][MAXPIECE+1][9][9][2]; // db subslice info

// dtc databases
extern dtcdatabase dtcsubdatabase[MAXPIECE+1][MAXPIECE+1][MAXPIECE+1][MAXPIECE+1][9][9][2];

// the cache
extern unsigned char *CacheBaseAddress; // allocate cache memory to this pointer
extern unsigned char **BlockPointer;	// pointers to the memory address of block #i, using unique block id i.
										// watch out for 32/64-bit compatibility problem
extern unsigned char *DTCCacheBaseAddress;
extern unsigned char **DTCBlockPointer;

extern int CacheSize; // the size of cache

// a doubly linked list for LRU caching
struct bi
{
	int64 uniqueid; // which block is in this
	int64 next;     // which is the index of the next blockinfo in the linked list
	int64 prev;     // which is the index of the last blockinfo in the linked list
}; 
extern bi* BlockInfo;
extern bi* DTCBlockInfo;

extern int64 Head,Tail; // array index of head and tail of the linked list.
extern int64 DTCHead, DTCTail;

// array for decoding
extern int RunLength[256]; // holds the run length for every byte
extern int Value[256];		// holds the value for every byte

// number of db buffers
extern int maxblocknum;
// max number of idx buffers
extern int maxidx;
extern int maxpieces;
extern int maxpiece;


void SetDatabaseReaderDirectory(const wchar_t* lpDir);
void SetReaderDatabaseName(wchar_t *dbname, int numPieces, bool index, bool dtc);
int ParseIndexFile(wchar_t* idxFilename, int blockoffset, int fpcount);
int ParseDtcIndexFile(wchar_t* idxFilename, int blockoffset, int fpcount);
int PreloadDatabase();
int ReaderDbInit(int suggestedMB);
void ReaderDbExit();

#define MAXFP 50
extern FILE *dbfp[MAXFP]; // file pointers to db2...dbn - always open.
extern FILE *dtcdbfp[MAXFP]; // file pointers to db2...dbn - always open.


template<typename BB, int MaxField, int BitsPerRow>
int ReaderDbLookup(BB *q, int cl, uint8 *pDTC)
{
	int64 index;
	int64 bm, bk, wm, wk, bmrank=0, wmrank=0;
	*pDTC = 0;
	
	int i;
	int blocknumber;	
	int DTCblocknumber;
	int uniqueblockid;
	int DTCuniqueblockid;
	int reverse = 0;
	int returnvalue=UNKNOWN;
	int memsize = 0;
	unsigned char *diskblock;
	unsigned char *dtcdiskblock;
	int64 newhead,prev, next;
	int n;
	int dtcn;
	int *idx;
	int *dtcidx;
	unsigned char byte;
	BB revpos;
	BB p;
	cprsubdb *dbpointer;
	dtcdb *dtcpointer;
	int color = cl;

	p=*q;

	// calculate number of pieces
	wm = BitCountXX<BB>(p.wm);
	wk = BitCountXX<BB>(p.wk);
	bm = BitCountXX<BB>(p.bm);
	bk = BitCountXX<BB>(p.bk);

	if(wm + wk == 0)
	{
		if(color == WHITE)
			return LOSS;
		else
			return WIN;
	}

	if(bm + bk == 0)
	{
		if(color == WHITE)
			return WIN;
		else
			return LOSS;
	}

	if( (bm+wm+wk+bk>maxpieces) || (bm+bk>maxpiece) || (wm+wk>maxpiece))
		return UNKNOWN;	
	
	// decide maximum rank of men
	if(wm)
		wmrank = MSBxx<BB>(p.wm)/BitsPerRow;
	if(bm)
		bmrank = (MaxField-LSBxx<BB>(p.bm))/BitsPerRow;	
	
	// if this BitBoard is dominated by the "wrong" side, we have to
	// reverse it!
	if (( ((bm+bk-wm-wk)<<(BitsPerRow*4ull)) + ((bk-wk)<<(BitsPerRow*2ull)) + ((bmrank-wmrank)<<BitsPerRow) + (1ull-cl)) > 0)
		reverse = 1;
	
	if (reverse)
	{
		// white is dominating this BitBoard, change colors
		revpos.wm = RevertXX<BB>(p.bm);
		revpos.wk = RevertXX<BB>(p.bk);
		revpos.bm = RevertXX<BB>(p.wm);
		revpos.bk = RevertXX<BB>(p.wk);
		color = color^1;
		p = revpos;
		
		reverse = bm;
		bm = wm;
		wm = reverse;
		
		reverse = bk;
		bk = wk;
		wk = reverse;
		
		reverse = bmrank;
		bmrank = wmrank;
		wmrank = reverse;
		
		reverse = 1;
	} // if
	else
	{
		int j = 0;
	}


	// get pointer to db
	dbpointer = &cprsubdatabase[wm][wk][bm][bk][wmrank][bmrank][color];
	dtcpointer = &dtcsubdatabase[wm][wk][bm][bk][wmrank][bmrank][color];
	
	if(dbpointer->ispresent == 0 || (dbfp[dbpointer->fp] == NULL))
		return UNKNOWN;
	if(dtcpointer->ispresent == 0 || (dbfp[dtcpointer->fp] == NULL))
		return UNKNOWN;

	
	bool oSingleValue = false;
	// check if the db contains only a single value
	if(dbpointer->value != UNKNOWN)
	{
		returnvalue =  dbpointer->value;
		oSingleValue = true;
	}

	// calculate index
	BitBoardToIndexXX<BB>(&p, &index);

	// now we know the index, and the database, so we look in the .idx file to find the
	// right memory block
	idx = dbpointer->idx;
	n= dbpointer->numberofblocks;
	dtcidx = dtcpointer->idx;
	dtcn= dtcpointer->numberofblocks;

	// now the array idx[] contains the index number at the start of every block.
	// we do a stupid linear search to find the block:
	blocknumber=0;
	while((blocknumber<n) && ((unsigned int)idx[blocknumber] <= index) )
		blocknumber++;
	// we overshot our target - blocknumber is now the first larger idx.
	blocknumber--;

	DTCblocknumber=0;
	while((DTCblocknumber<dtcn) && ((unsigned int)dtcidx[DTCblocknumber] <= index) )
		DTCblocknumber++;
	// we overshot our target - blocknumber is now the first larger idx.
	DTCblocknumber--;


	// we now have the blocknumber inside the database slice in which the BitBoard is located.
	// get the unique number which identifies this block
	uniqueblockid = dbpointer->blockoffset+
					dbpointer->firstblock +
					blocknumber;

	DTCuniqueblockid = dtcpointer->blockoffset+
					dtcpointer->firstblock +
					DTCblocknumber;


	if(oSingleValue == false)
	{
		// check if it is loaded:
		if(BlockPointer[uniqueblockid] != NULL)
		{
			diskblock = BlockPointer[uniqueblockid];
		
			// update LRU linked list
			// 1): which index does this block have in the array?
			newhead = (diskblock - CacheBaseAddress)/1024;
			if(newhead!=Head)
			{
				if(newhead == Tail)
				{
					// special case: we will have to reset tail too
					prev = BlockInfo[newhead].prev;
					BlockInfo[prev].next = -1;
					Tail = prev;
				
					BlockInfo[Head].prev = newhead;
					BlockInfo[newhead].next = Head;
					BlockInfo[newhead].prev = -1;
					Head = newhead;
				}
				else
				{
					// remove index "newhead" from doubly linked list:
					// relink previous and next block of newhead
					prev = BlockInfo[newhead].prev;
					next = BlockInfo[newhead].next;
					BlockInfo[prev].next = next;
					BlockInfo[next].prev = prev;
				
					// set info for newhead
					BlockInfo[newhead].prev = -1;
					BlockInfo[newhead].next = Head;

					// set info for head
					BlockInfo[Head].prev = newhead;
				
					Head = newhead;
				}
			}
		}
	
		else
		// we must load it
		{

			//------------------------------------------------
			// new LRU code
			// get address to load it to: write it into tail.

			diskblock = CacheBaseAddress + 1024*Tail;
			//printf("write to tail %i ",tail);
			// and save it in the blockpointer array
			BlockPointer[uniqueblockid]=diskblock;
			// reset blockpointer entry of whatever was there before
			// i use -1 for an empty cache block - nothing to unload...
			// blockpointer[blockid] points to the memory address
			if ( BlockInfo[Tail].uniqueid !=-1 )
			{
				BlockPointer[BlockInfo[Tail].uniqueid]=NULL;
			}
			// blockpointer array is updated

			// blockinfo array contains information on the linked list - must update it
			// steps: set current head's previous item
			//        get a new tail, and set it's next item to -1
			//        write new block into space where old tail was as new head
			//        set it's 3 items.

			newhead = Tail;
			Tail = BlockInfo[Tail].prev;
			BlockInfo[Tail].next = -1;
			BlockInfo[newhead].uniqueid=uniqueblockid;
			BlockInfo[newhead].next = Head;
			BlockInfo[newhead].prev = -1;
		
			BlockInfo[Head].prev = newhead;
			Head = newhead;

			// move to the disk block we are looking for
			//fseek(dbfp[bm+bk+wm+wk],blocknumber*1024,SEEK_SET);
			// now, we want to load a block. we have to seek the
			// right BitBoard - that is in this file, blocknumber+firstblock
			_fseeki64(dbfp[dbpointer->fp],(blocknumber+dbpointer->firstblock)*1024,SEEK_SET);
			// and read it
			//i = fread(diskblock,1024,1,dbfp[dbpointer->fp]);
			fread(diskblock,1024,1,dbfp[dbpointer->fp]);

		}
	}


	 /// DTC
	/////////////////////////////////////////////////////////////////////

	// check if it is loaded:
	if(DTCBlockPointer[DTCuniqueblockid] != NULL)
	{
		dtcdiskblock = DTCBlockPointer[DTCuniqueblockid];
		
		// update LRU linked list
		// 1): which index does this block have in the array?
		newhead = (dtcdiskblock - DTCCacheBaseAddress)/1024;
		if(newhead!=DTCHead)
		{
			if(newhead == DTCTail)
			{
				// special case: we will have to reset tail too
				prev = DTCBlockInfo[newhead].prev;
				DTCBlockInfo[prev].next = -1;
				DTCTail = prev;
				
				DTCBlockInfo[DTCHead].prev = newhead;
				DTCBlockInfo[newhead].next = DTCHead;
				DTCBlockInfo[newhead].prev = -1;
				DTCHead = newhead;
			}
			else
			{
				// remove index "newhead" from doubly linked list:
				// relink previous and next block of newhead
				prev = DTCBlockInfo[newhead].prev;
				next = DTCBlockInfo[newhead].next;
				DTCBlockInfo[prev].next = next;
				DTCBlockInfo[next].prev = prev;
				
				// set info for newhead
				DTCBlockInfo[newhead].prev = -1;
				DTCBlockInfo[newhead].next = DTCHead;

				// set info for head
				DTCBlockInfo[DTCHead].prev = newhead;
				
				DTCHead = newhead;
			}
		}
	}
	
	else
	// we must load it
	{

		//------------------------------------------------
		// new LRU code
		// get address to load it to: write it into tail.

		dtcdiskblock = DTCCacheBaseAddress + 1024*DTCTail;
		//printf("write to tail %i ",tail);
		// and save it in the blockpointer array
		DTCBlockPointer[DTCuniqueblockid]=dtcdiskblock;
		// reset blockpointer entry of whatever was there before
		// i use -1 for an empty cache block - nothing to unload...
		// blockpointer[blockid] points to the memory address
		if ( DTCBlockInfo[DTCTail].uniqueid !=-1 )
		{
			DTCBlockPointer[DTCBlockInfo[DTCTail].uniqueid]=NULL;
		}
		// blockpointer array is updated

		// blockinfo array contains information on the linked list - must update it
		// steps: set current head's previous item
		//        get a new tail, and set it's next item to -1
		//        write new block into space where old tail was as new head
		//        set it's 3 items.

		newhead = DTCTail;
		DTCTail = DTCBlockInfo[DTCTail].prev;
		DTCBlockInfo[DTCTail].next = -1;
		DTCBlockInfo[newhead].uniqueid=DTCuniqueblockid;
		DTCBlockInfo[newhead].next = DTCHead;
		DTCBlockInfo[newhead].prev = -1;
		
		DTCBlockInfo[DTCHead].prev = newhead;
		DTCHead = newhead;

		// move to the disk block we are looking for
		//fseek(dbfp[bm+bk+wm+wk],blocknumber*1024,SEEK_SET);
		// now, we want to load a block. we have to seek the
		// right BitBoard - that is in this file, blocknumber+firstblock
		_fseeki64(dtcdbfp[dbpointer->fp],(DTCblocknumber+dtcpointer->firstblock)*1024,SEEK_SET);
		// and read it
		//i = fread(diskblock,1024,1,dbfp[dbpointer->fp]);
		fread(dtcdiskblock,1024,1,dtcdbfp[dbpointer->fp]);
	}


	// the block we were looking for is now pointed to by diskblock
	// and it has been moved to the head of the linked list
	// now we decompress the memory block
	// get n, the offset index for this diskblock
	
	if(oSingleValue == false)
	{
		reverse=0;
		if(	dbpointer->numberofblocks>blocknumber+1)
			//we are not at the last block
		{
			if(idx[blocknumber+1]-index < index-idx[blocknumber])
				reverse=1;
		}

		if(reverse)
		{
			n=idx[blocknumber+1];
			i=1023;
			while((unsigned int)n>index /*&& i>=0*/)
			{
				n-=RunLength[diskblock[i]];
				i--;
			}
			i++;
		}
		else
		{
			n = idx[blocknumber];

			// and move through the bytes until we overshoot index
			// set the start byte in this block: if it's block 0 of a db, this can be !=0
			i=0;
			if(blocknumber == 0)
				i = dbpointer->startbyte;

			// LOOKUP LOOP - executes about 500 times on average
			// this could be speeded up by storing the index of the first 512 loops

			while((unsigned int)n<=index /*&& i<1024*/)
			{
				n+=RunLength[diskblock[i]];
				i++;
			}
		
			i--;
			n-=RunLength[diskblock[i]];
		}
	}

	 // DTC
	/////////////////////////////////
	dtcn = dtcidx[DTCblocknumber];

	// and move through the bytes until we overshoot index
	// set the start byte in this block: if it's block 0 of a db, this can be !=0
	int ii=0;
	if(DTCblocknumber == 0)
		ii = dtcpointer->startbyte;

	// LOOKUP LOOP - executes about 500 times on average
	// this could be speeded up by storing the index of the first 512 loops

	while((unsigned int)dtcn<=index /*&& i<1024*/)
	{
		if(dtcdiskblock[ii]>=81)
			dtcn += skipDTC[dtcdiskblock[ii] - 81], ii++;
		else
			dtcn++;
		ii++;
	}	
	ii--;

	
	
	if(oSingleValue == false)
	{
		// finally, we have found the byte which describes the BitBoard we
		// wish to look up. it is diskblock[i].
		//if(reverse)
		//	printf("\nbytenumbers reverse: %i nonreverse %i",bytenumber,i);	
		if(diskblock[i]>80)
		{
			// t'was a compressed byte - easy
			returnvalue = Value[diskblock[i]];
			byte = diskblock[i];
		}
		else
		{
			// an uncompressed byte
			byte = diskblock[i];
			i = (int)(index-n); // should be 0,1,2,3

			switch(i)
			{
				case 0:
					returnvalue = byte % 3;
					break;
				case 1:
					returnvalue = (byte/3) % 3;
					break;
				case 2:
					returnvalue = (byte/9) % 3;
					break;
				case 3:
					returnvalue = (byte/27) % 3;
					break;
			}
		}

		returnvalue++;
	}

	 // DTC
	/////////////////

	uint8 value = dtcdiskblock[ii];
	*pDTC = value;

	return returnvalue;
}


}

